#include<iostream>
#include<fcntl.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sstream>
#include<string.h>
#include<fstream>
#include<stdlib.h>
#include<errno.h>
#include<vector>
#include"CommandInterpreter.cpp"
#include"Internal.cpp"
using namespace std;
class CommandExecutor {
	public:
	int current_in;
	int current_out;
	static Hidden hidden;
	SignalHandler sHandler;
	CommandExecutor() {
		current_in = 0;
		current_out = 1;
	}

	static void exit_shell(int k) {
		Hidden::stop();
		History::flush();
		cout<<"Thanks for using the shell";
		if(k) cout<<"..... 'exit' command can also be used for exiting from shell"; 
		cout<<endl;
		exit(0);
	}

	void parseAndExecute() {
		string temp;
		sHandler.setupSignalHandlers();
		Utility::sys_start();
		cout<<"Allen@Turing:-"<<Internal::getCWD()<<"$ ";
		Utility::sys_end();
		while(getline(cin, temp)) {
			SignalHandler::setFlag(flag);
			vector<Command> commands = separatePipe(temp);		
			execute(commands);
			Hidden::check();
			Utility::sys_start();
			cout<<endl<<"Allen@Turing:-"<<Internal::getCWD()<<"$ ";
			Utility::sys_end();
		}
	}
	
	static void execute(vector<Command> commands) {
		int size=commands.size();
		if(size>0) {
			string in = commands[0].getInputFile();
			string out = commands[size-1].getOutputFile();
			int in_fd = Utility::openFile(in, true, commands[0].isInAppend());
			int out_fd = Utility::openFile(out, false, commands[size-1].isOutAppend());
			execute(commands,in_fd,out_fd,size);
		}
	}

	vector<Command> separatePipe(string input) {
		string temp;
		vector<Command> commands;
		if(input != "") {
			stringstream ss(input);
			while(getline(ss, temp, '|')) {
				if(temp != "") {
					temp = Utility::trim(temp);
					if(temp == "exit") {
						Exit::exit_shell(0);
					}
					History::add(temp);
					Command command = CommandInterpreter::checkRedirection(temp);
					//Check if valid
					commands.push_back(command);
				}
			}
		}
		return commands;
	}

	static int execute_single(Command command) {
		vector<Command> commands;
		commands.push_back(command);
		execute(commands);
	}

	static int execute(vector<Command> commands, int in, int out, int n) {
		int i,j,pid,st;
		commands[0].setInputStream(in);
		commands[n-1].setOutputStream(out);
		for(i=0;i<n-1;i++) {
			int fd[2];
			if(pipe(fd)==-1) {
				Utility::err_start();
				cerr<<"Error creating pipe"<<endl;
				Utility::err_end();
				return -1;
			}
			commands[i+1].setInputStream(fd[0]);
			commands[i].setOutputStream(fd[1]);
		}
		bool flag = Utility::checkInternal(commands[0].getCommand(), true);
		if(flag) {
			executeInternal(commands[0]);
			return 0;
		} 
		if(Utility::startswith(commands[0].getCommandWithArgs(), "!")) {
			string s = History::find(commands[0].getCommandWithArgs().substr(1));
			if(s != "") {
				Command command = CommandInterpreter::checkRedirection(s);
				execute_single(command);
			} else cout<<commands[0].getCommandWithArgs()<<"::Event not found"<<endl;
			return 0;				
		}	
		for(i=0;i<n;i++) {
			pid=fork();
			if(pid<0) {
				Utility::err_start();
				cerr<<"Error creating child process"<<endl;
				Utility::err_end();
				return -1; 
			} else if (pid>0) {
				if(commands[i].isBackground()) {
					cout<<"["<<pid<<"]"<<endl;
					Hidden::add(pid);
				}
			} else {
				if(commands[i].getOutputStream() != -1) {
					if(dup2(commands[i].getOutputStream(), 1) ==-1) {
						Utility::err_start();
						cerr<<"Error in dup2"<<endl;
						Utility::err_end();
						return -1;	
					}
				}				
				if(commands[i].getInputStream() != -1) {
					if(dup2(commands[i].getInputStream(), 0) ==-1) {
						Utility::err_start();
						cerr<<"Error in dup2"<<endl;
						Utility::err_end();
						return -1;	
					}
				}
				for(j = 0; j < n; j++) {				
					close(commands[j].getInputStream());
					close(commands[j].getOutputStream());
				}
				execute(commands[i]);
			}
		}
		for(j = 0; j < n; j++) {				
			close(commands[j].getInputStream());
			close(commands[j].getOutputStream());
		}
		if(commands[n-1].isBackground()) return 0;
		waitpid(pid,NULL,0);
		while (1) {
                	waitpid(pid,NULL,0);
                	if(errno== ECHILD) {
                    		break;
                	}
                }
	}

	static void execute(Command command) {
		bool flag = Utility::checkInternal(command.getCommand(),false);
		if(flag) {
			executeInternal(command);
			exit(0);
		} else {
			executeExternal(command);
		}	
	}

	

	static void executeInternal(Command command) {
		Internal::execute(command);
	}

	static void executeExternal(Command command) {
		Utility::out_start();
		//Utility::err_end();
		fflush(stdout);
		execvp(command.getCommand().c_str(), Internal::tokenize(command.getCommandWithArgs(), ' '));
		Utility::out_end();
		Utility::err_start();
		cout<< command.getCommand()<<" :Command not found"<<endl;
		Utility::err_end();
		exit(1);
	}
};

int main() {
	CommandExecutor inter;
	inter.parseAndExecute();
}
